#include <Hardware.h>
#include <View.h>

#include <GxEPD2_BW.h>

#ifdef USE_U8G2
#include <U8g2_for_Adafruit_GFX.h>
#define FONT u8g2_font_wqy16_t_gb2312
U8G2_FOR_ADAFRUIT_GFX u8Mx;
#else
#include <u8Matrix.h>
#include <simsun_16.h>
#define FONT simsun_16
U8Matrix u8Mx;
#endif

#include <RX8025.h>
#include <Lunar.h>

#ifdef USE_BatterySense
#include <Battery.h>
#endif

/*
由于U8g2库在处理字库内没有的字符时，就略过该字符，我修改了以下代码，使屏幕为该字符留空

U8g2_for_Adafruit_GFX.cpp

static int16_t u8g2_font_draw_glyph(u8g2_font_t *u8g2, int16_t x, int16_t y, uint16_t encoding)
{
    //int16_t dx;
    static int16_t dx;
*/

RTC_DATA_ATTR int8_t tomatoMin; // 番茄钟起始分
RTC_DATA_ATTR int8_t tomatoSec; // 焦点行起始秒

// LED数字显示
// 每个数字点亮的划
char DG[10] = {119, 68, 62, 110, 77, 107, 123, 70, 127, 111};
// 每划的起始坐标 0，2，4，6 竖划，1，3，5 横划
char offBig[7][2] = {{1, 2}, {0, 0}, {30, 2}, {0, 35}, {1, 37}, {0, 70}, {30, 37}};

// 墨水屏显示控制
#ifdef REMAP_EPD_SPI
SPIClass hspi(HSPI);
#endif

// 根据墨水屏型号来设置
GxEPD2_BW<GxEPD2_371, GxEPD2_371::HEIGHT> display(GxEPD2_371(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY));

bool View::hasInit = false;

char const *weekString[7] = {"日", "一", "二", "三", "四", "五", "六"};

// 时间到鸣响
void beep(uint frequency, uint duration)
{
    pinMode(BEEP_IO, OUTPUT);
    tone(BEEP_IO, frequency);
    delay(duration);
    noTone(BEEP_IO);
}

/**
 * @brief 绘制液晶数字
 */
void drawDigitBig(int sx, int sy, char digit, uint16_t dColor = BLACK_COLOR)
{
    int x, y, w, h;
    char dIndex = DG[digit & 0x0f];
    for (int l = 0, b = 1; l < 7; l++, b <<= 1)
    {
        if (dIndex & b)
        {
            x = sx + offBig[l][0];
            y = sy + offBig[l][1];

            if (l % 2) // 第1,3,5划(横)
            {
                w = 20; // 40-10*2
                h = 9;
                x += 10;

                for (int i = 0; i < 2; i++)
                {
                    int y1 = y, y2 = y + 8, tx = i ? x : x + w - 1;
                    while (y2 > y1)
                    {
                        tx += i ? -1 : 1;
                        display.drawLine(tx, y1, tx, y2, dColor);
                        // y1++;y2--
                        if (l >= 3)
                            y1++;
                        if (l <= 3)
                            y2--;
                    }
                }
            }
            else // 第0,2,4,6划(竖)
            {
                w = 9;
                h = 20;
                y += 10;

                for (int i = 0; i < 2; i++)
                {
                    int x1 = x, x2 = x + 8, ty = i ? y : y + h - 1;
                    while (x2 > x1)
                    {
                        ty += i ? -1 : 1;
                        display.drawLine(x1, ty, x2, ty, dColor);
                        // x1++;x2--
                        if ((l <= 2 && i == 0) || (l >= 4 && i == 1))
                        {
                            x1++;
                            x2--;
                        }
                        else
                        {
                            if (l % 4)
                                x1++;
                            else
                                x2--;
                        }
                    }
                }
            }
            display.fillRect(x, y, w, h, dColor);
        }
    }
}

/**
 * @brief 绘制电池电量
 */
void drawBattery()
{
#ifdef USE_BatterySense
    Battery bat(3400, 4200, BATTERY_READ_IO);
    bat.onDemand(WAKE_IO, HIGH);
    bat.begin(4200, 0.5);

    // 使用简易非线性电量计算方法
    bat.begin(4200, 0.5, &sigmoidal);
    // display.fillRect(198, 404, bat.level() >> 2, 10, BLACK_COLOR); // 不分格电量
#ifdef SHOW_VOLTAGE
    int c = bat.voltage();
#else
    int c = bat.level() / 20; // 分格电量
#endif
#else
    int c = batteryLevel();
#endif
#ifdef SHOW_VOLTAGE
    char strVoltage[8];
    sprintf(strVoltage, "%3.2f", c/1000.0);
    u8Mx.drawUTF8(205, 416, strVoltage);
    //display.drawRoundRect(196, 402, 35, 14, 2, BLACK_COLOR);
#else
    for (int i = 0; i < c; i++)
    {
        display.fillRect(198 + i * 5, 404, 4, 10, BLACK_COLOR);
    }

    display.drawRoundRect(196, 402, 29, 14, 2, BLACK_COLOR);
    display.drawRect(224, 406, 4, 6, BLACK_COLOR);
    display.drawLine(224, 406, 224, 411, WHITE_COLOR);
#endif
}

/**
 * @brief 绘制状态行
 */
void drawStatusBar()
{
    TIME_TM tm;
    char dateString[30];

    rxTime.getTime(&tm);

    // 底部日期时间
    sprintf(dateString, "%4d-%02d-%02d %02d:%02d 星期%s", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, weekString[tm.tm_wday]);
    u8Mx.drawUTF8(10, SCREEN_HEIGHT - 1, dateString);
    // 电池电量
    drawBattery();
}

void View::refresh()
{
    if (!hasInit)
    {

#ifdef REMAP_EPD_SPI
        hspi.begin(SCK, MISO, MOSI, EPD_CS); // remap hspi for EPD (swap pins)
        display.epd2.selectSPI(hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0));
#endif

#ifdef DEBUG_ON
        display.init(115200, false, 2, false); // 串口使能
#else
        display.init(0, false, 2, false); // 串口禁能
#endif

        u8Mx.begin(display);
        u8Mx.setFont(FONT);

#ifdef USE_U8G2
        u8Mx.setForegroundColor(BLACK_COLOR); // 设置前景色
        u8Mx.setBackgroundColor(WHITE_COLOR); // 设置背景色
#endif

        hasInit = true;
    }

    display.fillScreen(WHITE_COLOR);

    updateView();

    display.display(false);
    display.hibernate();
}

void Menu::handleKey(int key)
{
    if (key == KEY_UP)
        rtcPgIdx += 3;
    if (key == KEY_DN)
        rtcPgIdx++;
    rtcPgIdx %= 4;
}

// 主菜单显示位置及菜单项高度
#define MENU_TOP 130
#define MENU_LEFT 70
#define MENU_ITEM_HEIGHT 48
#define MENU_ITEM_LEFT 88

void Menu::updateView()
{
    for (int i = 0, y = MENU_TOP; i < 4; i++)
    {
        u8Mx.drawUTF8(MENU_ITEM_LEFT, y, item[i]);
        y += MENU_ITEM_HEIGHT;
    }
    display.drawRoundRect(MENU_LEFT, MENU_TOP + rtcPgIdx * MENU_ITEM_HEIGHT - TEXT_SIZE, 100, LINE_HEIGHT, 3, BLACK_COLOR);

    drawStatusBar();
}

void Clock::handleKey(int key)
{
    // rxTime.getTime(&tm);
    // randomSeed(rxTime.getTime());

    if (key == KEY_MD) // 时钟显示时，长按右键网络对时
    {
        syncing = true;
        refresh();

        syncTime();

        // rxTime.getTime(&tm);
    }
    else
    {
        randomSeed(rxTime.getTime());
    }
    rxTime.getTime(&tm);
}

#define DG_WIDTH 44
#define DG_XOFF 12
void Clock::updateView()
{
    if (syncing)
    {
        display.drawRoundRect(64, 184, 104, 32, 3, BLACK_COLOR);
        u8Mx.drawUTF8(74, 204, (char *)"网络对时...");
        syncing = false;
    }
    else
    {
        // 设置随机显示位置
        int yOff = 16 + (random() % 300);

        // 液晶大时分
        drawDigitBig(DG_XOFF, yOff, tm.tm_hour / 10);
        drawDigitBig(DG_XOFF + DG_WIDTH, yOff, tm.tm_hour % 10);
        drawDigitBig(DG_XOFF + DG_WIDTH * 3, yOff, tm.tm_min / 10);
        drawDigitBig(DG_XOFF + DG_WIDTH * 4, yOff, tm.tm_min % 10);
        display.fillRect(DG_XOFF + DG_WIDTH * 2 + 15, yOff + 25, 10, 10, BLACK_COLOR);
        display.fillRect(DG_XOFF + DG_WIDTH * 2 + 15, yOff + 50, 10, 10, BLACK_COLOR);

        // 底部日期时间
        char dateString[40];
        sprintf(dateString, "%4d/%02d/%02d 星期%s ", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, weekString[tm.tm_wday]);
        // 农历
        Lunar lunar;
        lunar.GetLunarString(&tm, dateString + 21);
        u8Mx.drawUTF8(DG_XOFF, yOff + 100, dateString);

        // 分钟变化2秒后及时醒来更新
        wakeUpSeconds = 62 - tm.tm_sec;
    }
}

void Gaze::updateView()
{
    int cx, cy; // 中心位置

    rtcPgIdx -= 2;
    if (rtcPgIdx <= 0)
    {
        rtcPgIdx = 30;
    }

    // 图形方向随机显示
    randomSeed(rxTime.getTime());
    int r = random() % 4;
    if (r % 2)
    {
        cx = 208;
        cy = 120;
    }
    else
    {
        cx = 120;
        cy = 208;
    }

    display.setRotation(r);

    int w = rtcPgIdx * 7;
    int left = cx - (w >> 1);
    int top = cy - (w >> 1);
    display.fillRect(left, top, w, rtcPgIdx, BLACK_COLOR);
    display.fillRect(left, cy - (rtcPgIdx >> 1), w, rtcPgIdx, BLACK_COLOR);
    display.fillRect(left, top + w - rtcPgIdx, w, rtcPgIdx, BLACK_COLOR);
    display.fillRect(left, top, rtcPgIdx, w, BLACK_COLOR);
}

// rtcPgIdx表示运行状态：
// bit6 0:预备中 1:运行中
// bit5 0:专注中 1:休息中
// bit0-bit4, 倒计时分钟数

// 00 0 预备中
// 01 1 无
// 10 2 专注中
// 11 3 休息中
#define TOM_REST 0x20
#define TOM_WORK 0x40
char const *tomatoString[4] = {"预备中", "", "专注中", "休息中"};

void Tomato::handleKey(int key)
{
    if (!(rtcPgIdx & TOM_WORK))
    {
        if (key == KEY_UP && rtcPgIdx > 5) // 以5为整次递减直至5
        {
            rtcPgIdx -= 5;
        }
        if (key == KEY_DN && rtcPgIdx < 30) // 以5为整次递增直至30
        {
            rtcPgIdx += 5;
        }
        if (key == KEY_MD) // 长按下键开始专注
        {
            TIME_TM tm;

            rxTime.getTime(&tm);
            tomatoMin = tm.tm_min;
            tomatoSec = tm.tm_sec;

            rtcPgIdx |= TOM_WORK;

            wakeUpSeconds = 60;
        }
    }
}

#define TG_XOFF 78
#define TG_YOFF 160
void Tomato::updateView()
{
    // 起始状态是时长为25分钟，设置状态
    if (rtcPgIdx == 0)
    {
        rtcPgIdx = 25;
    }

    if (rtcPgIdx & TOM_WORK) // 工作中
    {
        TIME_TM tm;

        rxTime.getTime(&tm);
        if (tm.tm_min == tomatoMin) // 分钟还未变，肯定未到点
        {
            wakeUpSeconds = 60 - tm.tm_sec + tomatoSec;
        }
        else // 分钟已变
        {
            if (tm.tm_sec < tomatoSec) // 分钟到了，秒未到
            {
                wakeUpSeconds = tomatoSec - tm.tm_sec;
            }
            else // 秒已到
            {
                wakeUpSeconds = 60 - tm.tm_sec + tomatoSec;

                // 更新时钟
                int n = rtcPgIdx & 0x1f;
                int tX = tm.tm_min - tomatoMin;
                n -= (tX > 0) ? tX : 60 + tX;
                tomatoMin = tm.tm_min;
                if (n <= 0) // 时间到！
                {
                    // 蜂鸣器响
                    //beep(400, 50);

                    if (rtcPgIdx & TOM_REST) // 休息->预备
                    {
                        n = 25;
                        rtcPgIdx = 0;
                        wakeUpSeconds = -1;
                    }
                    else // 专注->休息
                    {
                        n = 5;
                        rtcPgIdx |= TOM_REST;
                        tomatoMin = tm.tm_min;
                        tomatoSec = tm.tm_sec;
                        wakeUpSeconds = 60;
                    }
                }
                rtcPgIdx = (rtcPgIdx & 0xe0) | n;
            }
        }
    }

    // 专注状态要反黑显示
    uint16_t dColor = BLACK_COLOR;
    if ((rtcPgIdx & 0x60) == TOM_WORK)
    {
        display.fillRoundRect(TG_XOFF - 5, TG_YOFF - 5, 94, 90, 5, BLACK_COLOR);
        dColor = WHITE_COLOR;
    }

    // 液晶大时分
    drawDigitBig(TG_XOFF, TG_YOFF, (rtcPgIdx & 0x01f) / 10, dColor);
    drawDigitBig(TG_XOFF + DG_WIDTH, TG_YOFF, (rtcPgIdx & 0x01f) % 10, dColor);
    u8Mx.drawUTF8(TG_XOFF + 22, TG_YOFF - 40, tomatoString[rtcPgIdx >> 5]);
}

PageView::PageView(RTCPage *pContent)
{
    content = pContent;
}

void PageView::updateView()
{
    ctxY = 16;
    ctxOffset = 0;

    char *txt = content->readTitle();
    if (txt[0])
    {
        ctxY = drawText(txt, ctxY);
    }
    drawText(content->readContent(), ctxY);

    int focusLine = content->getFocusLine();
    if (focusLine >= 0)
    {
        display.drawRoundRect(0, focusLine * LINE_HEIGHT, SCREEN_WIDTH, LINE_HEIGHT, 3, BLACK_COLOR);
    }

    //  底部左侧时间
    char timeString[6];
    int hm = rxTime.getHMTime();
    sprintf(timeString, "%1d%1d:%1d%1d", hm >> 12, (hm >> 8) & 0x0f, (hm >> 4) & 0x0f, hm & 0x0f);
    u8Mx.drawUTF8(LEFT_SPACE, SCREEN_HEIGHT - 2, timeString);

    // 底部居中显示文件名
    txt = content->readCtxInfo();
    u8Mx.drawUTF8(120 - (u8Mx.getUTF8Width(txt) >> 1), SCREEN_HEIGHT - 2, txt);

    // 电池电量
    drawBattery();
}

int PageView::drawText(char *str, int y)
{
    char *f, *s;

    s = str + ctxOffset;
    do
    {
        if (y > SCREEN_HEIGHT)
        {
            break;
        }

        f = strchr(s, '\n'); // 查找换行符
        if (f)
        {
            (*f) = 0; // 截断以便显示当前字符串
        }

        u8Mx.drawUTF8(LEFT_SPACE, y, s);

        if (f)
        {
            (*f) = '\n'; // 恢复被截断的字符串
            s = f + 1;   // 调整字符串起始位置

            ctxOffset = f - str + 1;
        }
        else
        {
            ctxOffset = 0;
        }

        y += LINE_HEIGHT;
    } while (f);

    return y;
}

/*
番茄钟流水账 tomato.day
年月日,{[时间,时长],...},数量
最近7天记录，7*30=210，即每格间隔30像素，暂定每格宽25？高则5-30分钟分别定为：5,9,13,17,21,25,每格间距2
*/
// void readTomato()
// {

// }